package com.dbw.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

/**
 * 反射扩展工具类 添加了一些反射相关方法
 * 
 * @author dongbowen
 *
 */
public class ReflectExtUtil {

    private static Field[] getDeclaredVariableFields(Class<?> classType) {
        return selectVariableOrConstant(classType.getDeclaredFields(), true);
    }
    
    private static Field[] getDeclaredConstantFields(Class<?> classType) {
        return selectVariableOrConstant(classType.getDeclaredFields(), false);
    }

    /**
     * Get the fields of the class, from classType to topClassType(exclude)
     * 
     * @param classType
     * @param topClassType
     * @return
     */
    public static Field[] getVariableFields(Class<?> classType, Class<?> topClassType) {

        List<Field> fieldList = new ArrayList<Field>();
        while (classType != topClassType) {
            List<Field> subFieldList = new ArrayList<Field>();
            Field[] fields = getDeclaredVariableFields(classType);
            for (Field field : fields) {
                subFieldList.add(field);
            }
            Collections.sort(subFieldList, new FieldComparator());
            fieldList.addAll(0, subFieldList);
            classType = classType.getSuperclass();
        }
        return fieldList.toArray(new Field[0]);
    }

    /**
     * Get the fields of the class, from classType to Object(exclude)
     * 
     * @param classType
     * @return
     */
    public static Field[] getVariableFields(Class<?> classType) {
        return getVariableFields(classType, Object.class);
    }

    private static Field[] selectVariableOrConstant(Field[] fields, boolean selectVariable) {
        ArrayList<Field> fieldList = new ArrayList<Field>();
        for (Field field : fields) {
            int modifier = field.getModifiers();
            if(selectVariable) {
                if(!(Modifier.isFinal(modifier) && Modifier.isStatic(modifier))) {
                    fieldList.add(field);
                }
            } else {
                if((Modifier.isFinal(modifier) && Modifier.isStatic(modifier))) {
                    fieldList.add(field);
                }
            }
        }
        return fieldList.toArray(new Field[0]);
    }

    public static Field getFieldIgnoreCase(Class<?> classType, String name) {
        Field[] fields = getVariableFields(classType);
        for (Field field : fields) {
            if (field.getName().equalsIgnoreCase(name)) {
                return field;
            }
        }

        return null;
    }

    /**
     * 
     * @param object
     * @return
     */
    public static String toString(Object object) {
        if(object == null) {
            return "null";
        }
        
        StringBuffer buffer = new StringBuffer();
        Field[] fields = ReflectExtUtil.getVariableFields(object.getClass());
        buffer.append("{");
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                buffer.append(field.getName() + ":" + fieldToString(field, object) + ", ");
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
        buffer.append("}");

        return buffer.toString();
    }
    
    @SuppressWarnings("unchecked")
    private static String fieldToString(Field field, Object object) {
        if(isIntegerField(field)) {
            return integerFieldToString(field, object);
        }
        
        try {
            if (Collection.class.isInstance(field.get(object))) {
                StringBuffer buffer = new StringBuffer();
                buffer.append("[");
                Collection<Object> collection = (Collection<Object>) field.get(object);
                for (Object subObj : collection) {
                    buffer.append(toString(subObj));
                    buffer.append(",");
                }
                buffer.append("]");
                return buffer.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 

        return normalFieldToString(field, object);
    }
    
    private static boolean isIntegerField(Field field) {
        Class<?> fieldType = field.getType();
        if(!fieldType.isPrimitive()) {
            return false;
        }
        
        return fieldType == byte.class
                || fieldType == short.class
                || fieldType == int.class
                || fieldType == long.class;
    }
    
    private static String integerFieldToString(Field field, Object object) {
        
        String normalString = null;
        Object value = null;
        
        try {
            value  = field.get(object);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        } 
        
        HexAnno hexAnno = field.getAnnotation(HexAnno.class);
        if(hexAnno != null && hexAnno.shouldPrintHex()) {
            normalString = String.format(Locale.getDefault(), "%#x", value);
        } else {
            normalString = String.valueOf(value);
        }
        
        
        field.setAccessible(true);
        IntegerEnumAnno anno = field.getAnnotation(IntegerEnumAnno.class);
        if(anno == null || !anno.hasConstantDescription()) {
            return normalString;
        }
        
        Field[] descFields = getDeclaredConstantFields(object.getClass());
        for(Field descField : descFields) {
            descField.setAccessible(true);
            IntegerEnumAnno descFieldAnno = descField.getAnnotation(IntegerEnumAnno.class);
            if(descFieldAnno == null || !descFieldAnno.correspondingIntegerValueName().equalsIgnoreCase(field.getName())) {
                continue;
            }
            
            return String.format(Locale.getDefault(), "%s(%s)", normalString, descField.getName());
        }
        
        return normalString;
    }
    
    private static String normalFieldToString(Field field, Object object) {
        try {
            return String.valueOf(field.get(object));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        
        return "";
    }
    
    /**
     * Test whether childType is descendant of fatherType
     * 
     * @param childType
     * @param fatherType
     * @return
     */
    public static boolean isChild(Class<?> childType, Class<?> fatherType) {
        Class<?> currentType = childType;
        while(currentType != null) {
            if(currentType == fatherType) {
                return true;
            }
            currentType = currentType.getSuperclass();
        }
        
        return false;
    }
}

class FieldComparator implements Comparator<Field> {

    @Override
    public int compare(Field field1, Field field2) {

        DefinitionOrder order1 = field1.getAnnotation(DefinitionOrder.class);
        DefinitionOrder order2 = field2.getAnnotation(DefinitionOrder.class);

        if (order1 == null || order2 == null) {
            return 0;
        }

        return order1.order() - order2.order();
    }

}
